import java.io.IOException;

public class ConstructorArbolesTiny {

	public static final boolean DEBUG = true;
	private Token tact;
	private TinyGA gramaticaAtributos = new TinyGA();
	private AnalizadorLexicoTiny analizadorLexico;

	public ConstructorArbolesTiny(AnalizadorLexicoTiny analizadorLexico) {
		this.analizadorLexico = analizadorLexico;
	}

	public TinyGA.Sent parse() throws IOException {
		tact = analizadorLexico.sigToken();
		TinyGA.Sent aDeSent = recSent();
		rec(CatLexica.EOF);
		return aDeSent;
	}

	private TinyGA.Sent recSent() throws IOException {
		TinyGA.Decs aDeDecs = recDecs();
		rec(CatLexica.SEP);
		TinyGA.Exp0 aDeExp0 = recExp0();
		return sentR1(aDeDecs, aDeExp0);
	}

	private TinyGA.Decs recDecs() throws IOException {
		return recRDecs(decsR2(recDec()));
	}

	private TinyGA.Decs recRDecs(TinyGA.Decs ahDeRDecs_0) throws IOException {
		while (tokenActual(CatLexica.PYCOMA)) {
			rec(CatLexica.PYCOMA);
			TinyGA.Dec aDeDec = recDec();
			ahDeRDecs_0 = decsR1(ahDeRDecs_0, aDeDec);
		}
		return ahDeRDecs_0;
	}

	private TinyGA.Dec recDec() throws IOException {
		Token tDeId = tact;
		rec(CatLexica.ID);
		rec(CatLexica.IGUAL);
		Token tDeNum = tact;
		rec(CatLexica.NUM);
		return decR1(tDeId, tDeNum);
	}

	private TinyGA.Exp0 recExp0() throws IOException {
		TinyGA.Exp1 aDeExp1 = recExp1();
		TinyGA.Exp0 aDeRExp0 = recRExp0(aDeExp1);
		TinyGA.Exp0 aDeRExp0_p = recRExp0_p(aDeRExp0);
		return aDeRExp0_p;
	}

	private TinyGA.Exp0 recRExp0(TinyGA.Exp1 ahDeRExp0) throws IOException {
		if (tokenActual(CatLexica.MUL)) {
			rec(CatLexica.MUL);
			TinyGA.Exp1 aDeExp1 = recExp1();
			return exp0R2(ahDeRExp0, aDeExp1);
		} else
			return exp0R3(ahDeRExp0);
	}

	private TinyGA.Exp0 recRExp0_p(TinyGA.Exp0 ahDeRExp0_p_0)
			throws IOException {
		while (tokenActual(CatLexica.SUMA)) {
			rec(CatLexica.SUMA);
			TinyGA.Exp1 aDeExp1 = recExp1();
			ahDeRExp0_p_0 = exp0R1(ahDeRExp0_p_0, aDeExp1);
		}
		return ahDeRExp0_p_0;
	}

	private TinyGA.Exp1 recExp1() throws IOException {
		if (tokenActual(CatLexica.NUM)) {
			Token tDeNum = tact;
			rec(CatLexica.NUM);
			return exp1R1(tDeNum);
		} else if (tokenActual(CatLexica.ID)) {
			Token tDeId = tact;
			rec(CatLexica.ID);
			return exp1R2(tDeId);
		} else if (tokenActual(CatLexica.PAP)) {
			rec(CatLexica.PAP);
			TinyGA.Exp0 aDeExp0 = recExp0();
			rec(CatLexica.PCIERRE);
			return exp1R3(aDeExp0);
		} else {
			errorSintactico(CatLexica.NUM, CatLexica.ID, CatLexica.PAP);
			return null;
		}
	}

	private void rec(CatLexica cat) throws IOException {
		if (tokenActual(cat)) {
			tact = sigToken();
		} else {
			errorSintactico(cat);
		}
	}

	private void errorSintactico(CatLexica... catsEsperadas) {
		System.err.print("(" + tact.fila() + "," + tact.col() + ")"
				+ "ERROR SINTACTICO: Encontrado " + tact
				+ ". Se esperaba alguno de los siguientes elementos: ");
		for (CatLexica catEsperada : catsEsperadas) {
			System.err.print(catEsperada + " ");
		}
		System.err.println();
		System.exit(1);
	}

	private boolean tokenActual(CatLexica... cats) {
		boolean encontrada = false;
		int i = 0;
		while (i < cats.length && !encontrada) {
			encontrada = tact.leeCategoria() == cats[i];
			i++;
		}
		return encontrada;
	}

	private Token sigToken() throws IOException {
		return analizadorLexico.sigToken();
	}

	private TinyGA.Sent sentR1(TinyGA.Decs decs, TinyGA.Exp0 exp0) {
		if (Config.DEBUG)
			return gramaticaAtributos.new SentR1Debug(decs, exp0);
		else
			return gramaticaAtributos.new SentR1(decs, exp0);
	}

	private TinyGA.Decs decsR1(TinyGA.Decs decs, TinyGA.Dec dec) {
		if (Config.DEBUG)
			return gramaticaAtributos.new DecsR1Debug(decs, dec);
		else
			return gramaticaAtributos.new DecsR1(decs, dec);
	}

	private TinyGA.Decs decsR2(TinyGA.Dec dec) {
		if (Config.DEBUG)
			return gramaticaAtributos.new DecsR2Debug(dec);
		else
			return gramaticaAtributos.new DecsR2(dec);
	}

	private TinyGA.Dec decR1(Token id, Token num) {
		if (Config.DEBUG)
			return gramaticaAtributos.new DecR1Debug(id, num);
		else
			return gramaticaAtributos.new DecR1(id, num);
	}

	private TinyGA.Exp0 exp0R1(TinyGA.Exp0 exp0, TinyGA.Exp1 exp1) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp0R1Debug(exp0, exp1);
		else
			return gramaticaAtributos.new Exp0R1(exp0, exp1);
	}

	private TinyGA.Exp0 exp0R2(TinyGA.Exp1 exp1_0, TinyGA.Exp1 exp1_1) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp0R2Debug(exp1_0, exp1_1);
		else
			return gramaticaAtributos.new Exp0R2(exp1_0, exp1_1);
	}

	private TinyGA.Exp0 exp0R3(TinyGA.Exp1 exp1) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp0R3Debug(exp1);
		else
			return gramaticaAtributos.new Exp0R3(exp1);
	}

	private TinyGA.Exp1 exp1R1(Token num) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp1R1Debug(num);
		else
			return gramaticaAtributos.new Exp1R1(num);
	}

	private TinyGA.Exp1 exp1R2(Token id) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp1R2Debug(id);
		else
			return gramaticaAtributos.new Exp1R2(id);
	}

	private TinyGA.Exp1 exp1R3(TinyGA.Exp0 exp0) {
		if (Config.DEBUG)
			return gramaticaAtributos.new Exp1R3Debug(exp0);
		else
			return gramaticaAtributos.new Exp1R3(exp0);
	}
}
